/*
  author Sylvain Bertrand <sylvain.bertrand@gmail.com>
  Protected by linux GNU GPLv2
  Copyright 2012-2014
*/
#include <linux/pci.h>
#include <linux/cdev.h>
#include <asm/unaligned.h>

#include <alga/rng_mng.h>
#include <uapi/alga/pixel_fmts.h>
#include <alga/timing.h>
#include <alga/amd/atombios/atb.h>
#include <uapi/alga/amd/dce6/dce6.h>
#include <alga/amd/atombios/vm.h>
#include <alga/amd/atombios/cm.h>
#include <alga/amd/atombios/pp.h>
#include <alga/amd/atombios/vram_info.h>

#include "../mc.h"
#include "../rlc.h"
#include "../ih.h"
#include "../fence.h"
#include "../ring.h"
#include "../dmas.h"
#include "../ba.h"
#include "../cps.h"
#include "../gpu.h"
#include "../drv.h"

#include "../smc.h"
#include "../smc_tbls.h"

#include "../regs.h"

#include "ctx.h"
#include "private.h"
#include "smc_lvl.h"
#include "smc_mc_reg_tbl.h"
#include "smc_mc_arb_tbl.h"

long smc_state_tbl_ulv_init(struct ctx *ctx, struct smc_state_tbl *tbl)
{
	long r;

	LOG("ultra low voltage smc table init");

	r = smc_lvl_from_atb(ctx, &ctx->atb_ulv.lvls[0], &tbl->ulv_lvl);
	if (r == -SI_ERR) {
		dev_err(&ctx->dev->dev, "dyn_pm:unable to init the ultra low voltage power level\n");
		return -SI_ERR;
	}

	/*--------------------------------------------------------------------*/
	/* patch the inited smc level */	
	tbl->ulv_lvl.state_flgs |= SMC_STATE_FLGS_DEEPSLEEP_BYPASS;
	tbl->ulv_lvl.mc_arb_set_idx = MC_ARB_SET_IDX_ULV;
	tbl->ulv_lvl.mc_reg_set_idx = MC_REG_SET_IDX_ULV;
	tbl->ulv_lvl.std_vddc = tbl->ulv_lvl.vddc;
	/*--------------------------------------------------------------------*/

	tbl->ulv.lvls_n = 1;
	tbl->ulv.flgs |= SMC_SW_STATE_FLGS_DC;
	return 0;
}

#define CG_ULV_CTL_DEFAULT	0x1f007550
#define CG_ULV_PARAM_DEFAULT	0x00040035
void ulv_program(struct pci_dev *dev)
{
	LOG("ultra low voltage state program");

	LOG("CG_ULV_CTL=0x%08x", CG_ULV_CTL_DEFAULT);
	LOG("CG_ULV_PARAM=0x%08x", CG_ULV_PARAM);

	wr32(dev, CG_ULV_CTL_DEFAULT, CG_ULV_CTL);
	wr32(dev, CG_ULV_PARAM_DEFAULT, CG_ULV_PARAM);
}

#define ULV_VOLT_CHANGE_DELAY_DEFAULT 1687
void smc_sw_regs_ulv_init(struct ctx *ctx)
{
	LOG("ultra low voltage smc software registers init");

	smc_sw_wr32(ctx->dev, ULV_VOLT_CHANGE_DELAY_DEFAULT,
						SMC_SW_ULV_VOLT_CHANGE_DELAY);
	/* actually the driver and probably the emergency state */
	smc_sw_wr32(ctx->dev, ctx->default_pcie_lanes_n,
						SMC_SW_NON_ULV_PCIE_LINK_WIDTH);
}

void smc_mc_reg_tbl_ulv_init(struct ctx *ctx,
					struct smc_mc_reg_tbl *smc_mc_reg_tbl)
{
	struct smc_mc_reg_set *set;
	/*
	 * For the ulv state, we are using the reg set for the lowest mem clk,
	 * namely the first set, since the table is sorted from lowest mem clk
	 * to highest. Yes, the mem clk from the atombios pwr lvl is ignored.
	 */
	set = &smc_mc_reg_tbl->sets[MC_REG_SET_IDX_ULV];
	smc_mc_reg_set_load(ctx, 0, set);
}

long smc_mc_arb_tbl_ulv_init(struct ctx *ctx,
					 struct smc_mc_arb_tbl *smc_mc_arb_tbl)
{
	u32 eng_clk;
	u32 mem_clk;
	struct smc_mc_arb_reg_set *ulv_set;
	long r;

	/*
	 * if there is no ulv state defined, don't init an arb reg set for it,
	 * since the one from the initial state will be used
	 */
	if (!ctx->atb_ulv.lvls_n)
		return 0;

	/* don't forget there is only one pwr lvl for the ulv state */
	eng_clk = ctx->atb_ulv.lvls[0].eng_clk;
	mem_clk = ctx->atb_ulv.lvls[0].mem_clk;

	ulv_set = &smc_mc_arb_tbl->sets[MC_ARB_SET_IDX_ULV];

	r = smc_mc_arb_tbl_set_compute(ctx, ulv_set, eng_clk, mem_clk);
	if (r == -SI_ERR) {
		dev_err(&ctx->dev->dev, "dyn_pm:unable to init the memory controller arb register set for the ultra low voltage state\n");
		return -SI_ERR;
	}
	return 0;
}
